import { DynamoDbService } from "./db_svc_impl/dynamodb_svc";
import { RedisService } from "./db_svc_impl/redis_svc";
import type { ClientConfig, DbService, DistributedLock, DynamoDbSvcConfig, ExtraData, RedisSvcConfig } from "./types";
import { sleep } from "./utils";

export class DistributedLockClient {

  private readonly heartbeat?: number;

  private readonly db_svc: DbService;

  /**
   * 构造方法
   * @param config - 客户端配置, 支持 DynamoDB 和 Redis(单机或者 Cluster) 作为后端数据库
   */
  constructor(config: ClientConfig) {
    this.db_svc = (config as DynamoDbSvcConfig).dynamodb === undefined
      ? new RedisService(config as RedisSvcConfig)
      : new DynamoDbService(config as DynamoDbSvcConfig);
    this.heartbeat = config.heartbeat;
  }

  /**
   * 尝试获取锁
   * @param id - 锁的 id
   * @param extra_data - 额外数据
   * @returns - 一个锁的实例, 暂时只包含释放锁的方法
   */
  readonly acquireLock = async (
    id: string,
    extra_data?: ExtraData
  ): Promise<DistributedLock> => {
    let rvn = "";
    while (true) {
      /** 根据 id 尝试获取 lock 对应的记录 */
      const item = await this.db_svc.getItem(id);
      /** 如果记录存在, 则再判断 rvn */
      if (item) {
        /** 如果 rvn 和之前记录的 rvn 不同, 说明锁仍然在被占用, 则等待 lease 过期再尝试重新获取锁 */
        if (rvn === item.rvn) {
          const new_rvn = await this.db_svc.grabLock(id, rvn, extra_data);
          /**
           * 如果 new_rvn 不为 null, 则说明强制获取锁成功
           * 否则说明锁被抢占, 则尝试重新获取记录
           */
          if (new_rvn) {
            rvn = new_rvn;
            break;
          }
        }
        /** 如果 rvn 和之前记录的 rvn 相同, 则直接尝试通过更新 rvn 的方式强制获取锁 */
        else {
          rvn = item.rvn;
          await sleep(item.lease);
        }
      }
      /** 如果记录不存在, 新建记录 */
      else {
        const new_rvn = await this.db_svc.createNewLock(id, extra_data);
        /**
         * 如果 new_rvn 不为 null
         * 说明记录创建成功, 则退出循环
         * 否则说明记录被抢先创建, 则尝试重新获取记录
         */
        if (new_rvn) {
          rvn = new_rvn;
          break;
        }
      }
    }
    /** 当设置了 heartbeat 时, 会不断更新 rvn, 防止锁被抢占 */
    let heartbeat_flag = this.heartbeat !== undefined;
    const complete_heartbeat = new Promise<void>(async (resolve) => {
      try {
        while (heartbeat_flag) {
          const new_rvn = await this.db_svc.sendHeartBeat(id, rvn);
          if (new_rvn) {
            rvn = new_rvn;
            await sleep(this.heartbeat!);
          }
          else {
            break;
          }
        }
      }
      catch (error) {
        console.error("error occurred while trying to send a heartbeat:", error);
      }
      finally {
        resolve();
      }
    });
    return {
      release: async (): Promise<void> => {
        heartbeat_flag = false;
        /**
         * 保证最后一次心跳发送完毕之后再尝试释放锁
         * 否则可能会出现心跳发送之后还没有返回结果
         * 此时客户端的 rvn 还没有更新, 但是数据库中的记录 rvn 已经更新
         * 导致 releaseLock 无法正常删除记录
         */
        await complete_heartbeat;
        await this.db_svc.releaseLock(id, rvn);
      }
    };
  };
}
